# Carregando as bibliotecas iniciais
import pandas as pd
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import squarify
import seaborn as sns
import datetime
import plotly.graph_objects as go
from plotly.graph_objs import *
from plotly.subplots import make_subplots
# Definir algumas configurações dos dataframes.
pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)
# Importando o DataSet para um dataframe do pandas.
df_full = pd.read_csv('./customer_sales.csv', sep=',')
# Visualizando as dimensões do dataset original
df_full.shape
# Olhando as primeiras linhas
df_full.head()
# Cria uma cópia do dataset original
df_full_tratado = df_full.copy()
df_full_tratado.shape
# Verificando os valores únicos
df_full_tratado.nunique()
# Remover do dataframe as colunas com valores constantes
df_full_tratado.drop(columns=['Z_CostContact', 'Z_Revenue'], inplace=True)
df_full_tratado.shape
# Verificando valores nulos
print('Valores Nulos\n')
print(df_full_tratado.isnull().sum())
# Cria a função para listar os atributos com o percentual valores nulos (Missing values)
def identifica_valores_faltantes(df):
nulo = df.isna().sum()
nulo = nulo[nulo>0]
nulo_perc = nulo/df.shape[0]
return pd.DataFrame({"Nulos" : nulo, "% Nulos" : nulo_perc})
# Lista a quantidade e percentual de valores NULOS por atributo.
identifica_valores_faltantes(df_full_tratado)
# Vamos remover os registros nulos (Missing values)
df_full_tratado.dropna(subset=['Income'], inplace=True);
df_full_tratado.shape
# Vamos Separar os Atributos Categóricos e Numéricos para facilitar no tratamento e análises.
# Lista de atributos que serão tratados como categóricos.
atributos_categoricos = ["Education", "Marital_Status", "Kidhome", "Teenhome", "AcceptedCmp1", "AcceptedCmp2", "AcceptedCmp3", "AcceptedCmp4", "AcceptedCmp5", "Complain"]
# Lista de atributos que serão tratado como Numéricos.
atributos_numericos = list(filter(lambda x: x not in atributos_categoricos, list(df_full_tratado.columns)))
# Lista os atriutos Numéricos
atributos_numericos
# Remove alguns atributos numéricos da lista.
atributos_numericos.remove('Dt_Customer')
atributos_numericos.remove('Year_Birth')
atributos_numericos.remove('ID')
atributos_numericos.remove('Response')
# Converter os tipos de algumas colunas.
# Vamos converter o atributo Dt_Customer para o tipo date
df_full_tratado['Dt_Customer'] = pd.to_datetime(df_full_tratado['Dt_Customer'], dayfirst=True)
# Obtém a data e hora atual
data_hora_atual = datetime.datetime.now()
# Adicionar uma coluna Age no dataset com a idade do cliente.
df_full_tratado['Age'] = data_hora_atual.year - df_full_tratado['Year_Birth']
# Vamos observar a Distribuição das idades para criar algumas faixas
df_full_tratado['Age'].hist();
# Vamos identificar as idades "inválidas". Para este estudo vamos considerar que qualquer idade acima de 85 anos é "inválida".
df_full_tratado[df_full_tratado['Age'] > 85]
# Vamos remover os registros com idades superior a 85 anos.
df_full_tratado = df_full_tratado[df_full_tratado['Age'] <= 85]
df_full_tratado.shape
# Vamos adicionar uma coluna de faixas etárias
df_full_tratado['Age_Group'] = np.where(df_full_tratado.Age<30, '20-29',
np.where((df_full_tratado.Age>29) & (df_full_tratado.Age<39) ,'30-39',
np.where((df_full_tratado.Age>39) & (df_full_tratado.Age<49) ,'40-59',
np.where((df_full_tratado.Age>49) & (df_full_tratado.Age<59) ,'50-59',
np.where((df_full_tratado.Age>59) & (df_full_tratado.Age<69) ,'60-69',
'>69')))))
# Adciona a coluna criada a lista de atributos categóricos.
atributos_categoricos.append('Age_Group')
df_full_tratado.shape
# Cria o método para ajudar a analisar os atributos categóricos do dataframe.
# Isso vai nos ajudar a observar como está a correlação dos atributos com o atributo alvo (target), além de observar quais atributos podemos unir ou descartar, por exemplo.
def desc_atributos_categoricos(df, atributos_categ, atributo_target, perc_obs_min):
col_agg = []
total_linhas = list(df.shape)[0]
dfm = None
for atrib in atributos_categ:
col_agg.append(df.groupby([atrib]).agg({atributo_target : ['count', 'mean', 'std']}))
dfm = pd.concat(col_agg, keys=atributos_categ)
perc_obs = dfm.iloc[:,0]/total_linhas
dfm = pd.DataFrame({'Observações' : dfm.iloc[:,0],
'Capacidade de Discriminação' : dfm.iloc[:,1],
'Desvio Padrão': dfm.iloc[:,2],
'% Observações' : perc_obs ,
'% Obs Maior que {0}%?'.format(str(round(perc_obs_min*100))): perc_obs > perc_obs_min}
)
return dfm
# Vamos exibir uma tabela da distribuição dos atributos em relação ao atributo alvo (target).
# Também vamos definir que o número mínimo de observações que cada atributo deve ser no mínimo 2% (valor empírico).
df_info_categ= desc_atributos_categoricos(df_full_tratado, atributos_categoricos, "Response", 0.02)
# Vamos aplicar um formatação para facilitar a visualização.
# O código abaixo destaca os atributos com a Capacidade de Discriminação inferior a 15% e número de observações menor que 2% (valores empíricos).
df_info_categ.style.applymap(lambda x: 'background-color: bisque' if x < 0.15 else 'None', subset=['Capacidade de Discriminação'])\
.applymap(lambda x: 'background-color: bisque' if x < 0.02 else 'None' , subset=['% Observações', '% Obs Maior que 2%?'])
# Nessa visualização podemos observar algumas coisas interessantes:
# 1 - Não temos nenhum atributo que se destaque em relação aos outros pela alta Capacidade de Discriminação nem pelo % Observações;
# 2 - Podemos observar que temos alguns atributos com Capacidade de Discriminação e % Observações muito baixos;
# 3 - Também que podemos identificar alguns atributos que podem ser agrupados, aumentando assim a Capacidade de Discriminação e o % Observações
# Vamos agrupar os atributos Teenhome e Kidhome em um novo
df_full_tratado['NumOffSprings'] = df_full['Kidhome'] + df_full['Teenhome']
df_full_tratado['NumOffSprings'] = df_full_tratado['NumOffSprings'].astype('category')
atributos_categoricos.append('NumOffSprings')
# Exibe os registros com Marital_Status que podem ser ruídos e não vamos utilizar.
df_full_tratado[(df_full_tratado['Marital_Status'] == 'Absurd') | (df_full_tratado['Marital_Status'] == 'YOLO')]
# Vamos remover os registros com estes Marital_Status incomuns
df_full_tratado = df_full_tratado[(df_full_tratado['Marital_Status'] != 'Absurd') | (df_full_tratado['Marital_Status'] != 'YOLO')]
df_full_tratado.shape
# Vamos criar um dataframe transformando os valores categóricos em valores inteiros para observar a correlação.
df_categ_code = pd.DataFrame()
# Vamos Converter os atributos catgóricos para o tipo category e codificar eles.
for c in atributos_categoricos:
df_categ_code[c] = df_full_tratado[c].astype('category')
df_categ_code[c] = df_categ_code[c].cat.codes
# Distribuição dos atributos catégoricos do dataset
df_categ_code.hist(figsize=(10,6))
plt.tight_layout()
plt.show()
# Plota o gráfico de correlação dos dados categóricos.
df_categ_corr = df_categ_code.astype(float).corr()
plt.figure(figsize=(20,8))
mask = np.triu(np.ones_like(df_categ_corr, dtype=np.bool))
ax1 = sns.heatmap(df_categ_corr,linewidths=0.1,vmax=1.0, square=True, annot=True, fmt='.2f', annot_kws={"size": 8} , cmap=matplotlib.cm.Blues, mask=mask)
plt.title('Correlação dos atributos categóricos\n')
plt.show()
# Adiciona o atributo Target ao dataframe dos valores categóricos codificados
df_categ_code["Response"] = df_full_tratado["Response"]
# Cria uma função para plotar graficos do tipo Violino.
def plota_grafico_violino(df, atributos, atributo_target, titulo_grafico, total_colunas = 5, altura =10, largura =20):
atributos_cop = atributos.copy()
if(atributo_target in atributos_cop):
atributos_cop.remove(atributo_target)
total_plots = len(atributos_cop)
total_linhas= total_plots//total_colunas + 1
fig, axes = plt.subplots(total_linhas, total_colunas, figsize=(largura, altura))
fig.subplots_adjust(hspace=0.3, wspace=0.4)
sns.set_style("whitegrid")
for i, atributo in enumerate(atributos_cop):
linha = i// total_colunas
coluna = i % total_colunas
sns.violinplot(data = df, y = atributo, x = atributo_target, ax=axes[linha, coluna])
plots_restantes = total_linhas * total_colunas - total_plots
for i in range(1,plots_restantes+1):
axes.flat[-i].set_visible(False)
fig.suptitle(titulo_grafico, y=0.95, fontsize=14, fontweight='bold')
# Plota a distribuição dos atributos Categóricos (codificados) pelo atributo Target (Response)
plota_grafico_violino(df_categ_code, atributos_categoricos, "Response", 'Distribuição dos atributos Categóricos pelo Target (Response)')
# Plota alguns graficos de barras com a ditribução de alguns atributos em relação ao target.
atributos_categoria_plot = ['Marital_Status', 'Education', 'Age_Group']
df = df_full_tratado
traces = []
linhas_plot = 2
colunas_plot = 2
fig = make_subplots(rows=linhas_plot, cols=colunas_plot, vertical_spacing = 0.25, subplot_titles=('Distribuição Response por Estado Civil', 'Distribuição Response por Escolaridade', 'Distribuição Response por Faixa Etária'))
for atributo in atributos_categoria_plot:
dados = df.groupby([atributo, 'Response'], as_index = False).agg({'ID': 'count'})
categoria = dados[atributo].unique()
dados_classe_0 = dados[dados.Response == 0]['ID']
dados_classe_1 = dados[dados.Response == 1]['ID']
traces.append( go.Bar(x=categoria, y=dados_classe_0, name='Response 0', showlegend=False, text=dados_classe_0 , textposition = 'auto', marker_color='indianred' ))
traces.append( go.Bar(x=categoria, y=dados_classe_1, name='Response 1', showlegend=False, text=dados_classe_1 , textposition = 'auto', marker_color='dodgerblue' ))
count = 0
for i, atributo in enumerate(atributos_categoria_plot):
linha = (i// colunas_plot) +1
coluna = (i % colunas_plot)+1
fig.append_trace(traces[count], row=linha, col=coluna)
fig.append_trace(traces[count+1], row=linha, col=coluna)
fig.update_xaxes(row=linha, col=coluna, title_text=atributos_categoria_plot[i])
count += 2
fig.update_yaxes(title_text='Quantidade')
fig.update_layout(font=dict( size=10))
fig.show()
# Plota as variáveis numéricas.
atributos_numericos
# Observando algumas medidas
df_full_tratado[atributos_numericos].describe().transpose()
# Distribuição dos atributos numéricos do dataset
df_full_tratado[atributos_numericos].hist(figsize=(10,6))
plt.tight_layout()
plt.show()
# Plota o gráfico de correlação dos dados Numéricos.
df_num_corr = df_full_tratado[atributos_numericos].corr()
plt.figure(figsize=(14,12))
mask = np.triu(np.ones_like(df_num_corr, dtype=np.bool))
ax1 = sns.heatmap(df_num_corr,linewidths=0.1,vmax=1, square=True, annot=True, fmt='.2f', annot_kws={"size": 9} , cmap=matplotlib.cm.Blues, mask=mask)
plt.title('Correlação dos atributos Numéricos\n')
plt.show()
# Plota a distribuição dos atributos Numéricos pelo atributo Target (Response)
plota_grafico_violino(df_full_tratado, atributos_numericos, "Response", 'Distribuição dos atributos Numéricos pelo Target (Response)')
df_full_tratado.shape
# Define a Função para plotar graficos BoxPlot
def plota_graficos_boxplot(df, lista_atributos, linhas_plot = 5, colunas_plot = 3):
traces = []
fig = make_subplots(rows=linhas_plot, cols=colunas_plot)
for atributo in lista_atributos:
traces.append(go.Box(y=df[atributo] , name = atributo))
count = 0
for i, atributo in enumerate(lista_atributos):
linha = (i// colunas_plot) +1
coluna = (i % colunas_plot)+1
fig.append_trace(traces[count], row=linha, col=coluna)
count +=1
fig.update_layout(title = "Distribuição das Variáveis Numéricas - Verificação de Outliers", title_x=0.5, font=dict( size=10), showlegend = False, height = 1200 )
fig.show()
# Vamos plotar os dados dos atributos numéricos indenticar outliers
plota_graficos_boxplot(df_full_tratado, atributos_numericos)
# Para a identificação de outlier vamos utilizar a biblioteca PyOD.
# PyOD é uma biblioteca que combina várias técnicas para detecção de outlier em dados multivariados.
# https://pyod.readthedocs.io/en/latest/index.html
# Para o nosso estudo vamos usar o KNN detector
from pyod.models.knn import KNN
# Aqui criamos e ajustamos o detector aos nossos dados.
detector_outlier = KNN()
detector_outlier.fit(df_full_tratado[atributos_numericos])
# Vamos coletar algumas informações sobre o resultado da detecção.
previsoes_outlier = detector_outlier.labels_ # Indica se o registro é outlier (1) ou não (0).
confianca_outlier = detector_outlier.decision_scores_ # Quanto mais alto o valor, mais anormal é o dados.
limite_outlier = detector_outlier.threshold_ # Define o valor limite para o dado não ser considerado um outlier (comparado com o decision_scores_)
# Cria um dataframe com a identificação de outlier no registro
df_outliers_detected = df_full_tratado.copy()
# Vamos adicionar as informações da detecção ao nosso dataframe.
df_outliers_detected['IsOutlier'] = previsoes_outlier
df_outliers_detected['OutlierConfidence'] = confianca_outlier
df_outliers_detected['OutlierConfidenceThreshold'] = confianca_outlier
# Salva do Dataframe em CSV
#df_outliers_detected.to_csv('df_outliers_detected.csv', index=False, sep=';', encoding='utf-8-sig')
# Lista somente os regitros marcados como outlier
df_full_tratado[df_outliers_detected['IsOutlier'] == 1].shape
# Existem diversas maneiras de tratamento de outliers, por exemplo: excluir, preencher com a média, tratar separado, transformação logaritmica, utilizar métodos de clusterização e etc..
# Neste estudo vamos remover os registros marcados como outlier.
df_full_tratado = df_full_tratado[df_outliers_detected['IsOutlier'] == 0].copy()
df_full_tratado.shape
# Vamos plotar os dados apóas o tratamento dos Outliers.
plota_graficos_boxplot(df_full_tratado, atributos_numericos)
# Soma o valor gasto com todos os tipos de produto.
df_full_tratado.loc[:, 'MntTotal'] = df_full_tratado['MntWines'] + df_full_tratado['MntFruits'] + df_full_tratado['MntMeatProducts'] + df_full_tratado['MntFishProducts'] + df_full_tratado['MntSweetProducts'] + df_full_tratado['MntGoldProds']
# Soma a quantidade de todos os tipos de produtos.
df_full_tratado.loc[:,'NumTotal'] = df_full_tratado['NumDealsPurchases'] + df_full_tratado['NumWebPurchases'] + df_full_tratado['NumCatalogPurchases'] + df_full_tratado['NumStorePurchases']
# Soma o total de campanhas aceitas
df_full_tratado.loc[:,'TotalAcceptedCmp'] = df_full_tratado['AcceptedCmp1'].astype(int) + df_full_tratado['AcceptedCmp2'].astype(int) + df_full_tratado['AcceptedCmp3'].astype(int) + df_full_tratado['AcceptedCmp4'].astype(int) + df_full_tratado['AcceptedCmp5'].astype(int)
# Calcula a proporçõo de campanhas aceitas
df_full_tratado.loc[:,'Prop_AcceptedCmp'] = df_full_tratado['TotalAcceptedCmp'] / 5
# Calcula a proporçao dos montantes de cada tipo
cols_mnt = ['MntWines', 'MntFruits', 'MntMeatProducts', 'MntFishProducts','MntSweetProducts', 'MntGoldProds']
for col in cols_mnt:
col_name= 'Prop' + col[3:len(col)]
df_full_tratado.loc[:,col_name] = df_full_tratado[col] / df_full_tratado['MntTotal']
# Calcula a proporçõo das quantidades
cols_num = ['NumDealsPurchases', 'NumWebPurchases','NumCatalogPurchases','NumStorePurchases']
for col in cols_mnt:
col_name= 'Prop' + col[3:len(col)]
df_full_tratado.loc[:,col_name] = df_full_tratado[col] / df_full_tratado['NumTotal']
df_full_tratado.head()
# Cria o dataframe RFM - Recência (R), Frequência (F), Monetaridade (M).
df_RFM = pd.DataFrame()
df_RFM ['ID'] = df_full_tratado['ID']
df_RFM ['Recency'] = df_full_tratado['Recency']
df_RFM ['Frequency']= df_full_tratado['NumTotal']
df_RFM ['Monetary'] = df_full_tratado['MntTotal']
# Distribuição das variáveis RFM - Recência (R), Frequência (F), Monetaridade (M).
print('\nDistribuição das varíaveis RFM - Recência (R), Frequência (F), Monetaridade (M).')
plt.figure(figsize=(12,10))
plt.subplot(3, 1, 1); sns.distplot(df_RFM['Recency'])
plt.subplot(3, 1, 2); sns.distplot(df_RFM['Frequency'])
plt.subplot(3, 1, 3); sns.distplot(df_RFM['Monetary'])
plt.show()
# Criando os quintis onde cada um contém 20% dos registros (população).
quintis = df_RFM[['Recency', 'Frequency', 'Monetary']].quantile([.2, .4, .6, .8]).to_dict()
quintis
# Cria o método para atribuir uma classificação a recência de 1 a 5 (Quanto menor melhor).
def r_score(x):
if x <= quintis['Recency'][.2]:
return 5
elif x <= quintis['Recency'][.4]:
return 4
elif x <= quintis['Recency'][.6]:
return 3
elif x <= quintis['Recency'][.8]:
return 2
else:
return 1
# Cria o método para atribuir uma classificação a Frequência e Monetaridade de 1 a 5 (Quanto maior melhor).
def fm_score(x, c):
if x <= quintis[c][.2]:
return 1
elif x <= quintis[c][.4]:
return 2
elif x <= quintis[c][.6]:
return 3
elif x <= quintis[c][.8]:
return 4
else:
return 5
# Cria o método para calcular o score RFM final. Vamos atribuir peso 3 a Recência (R) e 1.5 a Frequência
def rfm_score(df):
return (df.R * 3) + (((df.F * 1.5) + df.M) / 2 )
# Calcula os Scores do RFM e adiciona no dataframe
df_RFM['R'] = df_RFM['Recency'].apply(lambda x: r_score(x))
df_RFM['F'] = df_RFM['Frequency'].apply(lambda x: fm_score(x, 'Frequency'))
df_RFM['M'] = df_RFM['Monetary'].apply(lambda x: fm_score(x, 'Monetary'))
df_RFM['RFM_Concat'] = df_RFM['R'].map(str) + df_RFM['F'].map(str) + df_RFM['M'].map(str)
df_RFM['RFM_Score'] = df_RFM.apply(rfm_score, axis=1)
df_RFM.head()
Na análise RFM há diferentes tipos de segmentação. Neste estudo vamos utilizar onze segmentos já bastante conhecidos na literatura. A terminologia aqui utilizada é uma tradução livre do inglês. (https://blog.analystats.com/pt/2019/03/21/segmenta%C3%A7%C3%A3o-de-clientes-uma-an%C3%A1lise-rfm-em-knime/)
Abaixo estão os segmentos de RFM e seu significado que definimos para essa análise.
# Cria o método para atribuir o RFM_Score um segmento.
def rfm_level(df):
if df.RFM_Score > 15.5:
return 'Campeões'
elif df.RFM_Score > 14.5 and df.RFM_Score <= 15.5:
return 'Fiéis'
elif df.RFM_Score > 14.125 and df.RFM_Score <= 14.5:
return 'Potencialmente fiéis'
elif df.RFM_Score > 11.125 and df.RFM_Score <= 14.125:
return 'Novos clientes'
elif df.RFM_Score > 10.625 and df.RFM_Score <= 11.125:
return 'Promissores'
elif df.RFM_Score > 8.75 and df.RFM_Score <= 10.625:
return 'Precisam de atenção'
elif df.RFM_Score > 7.375 and df.RFM_Score <= 8.75:
return 'Quase em risco'
elif df.RFM_Score > 7.125 and df.RFM_Score <= 7.375 :
return 'Em Risco'
elif df.RFM_Score > 6.375 and df.RFM_Score <= 7.125:
return 'Não posso perdê-los'
elif df.RFM_Score > 4.25 and df.RFM_Score <= 6.375:
return 'Hibernando'
else:
return 'Perdidos'
# Cria a coluna de segmento RFM no dataframe
df_RFM['RFM_Segment'] = df_RFM.apply(rfm_level, axis=1)
# Salva do Dataframe em CSV
#df_RFM.to_csv('df_RFM.csv', index=False, sep=';', encoding='utf-8-sig')
# Exibe os 10 melhores clientes de acordo com RFM
print('Top 10 melhores clientes de acordo com RFM')
df_RFM.sort_values(by = ['RFM_Score','Monetary'] , ascending=False).head(10)
# Exibe os 10 piores clientes de acordo com RFM
print('Top 10 piores clientes de acordo com RFM')
df_RFM.sort_values(by = ['RFM_Score','Monetary'] , ascending=True).head(10)
# Agrupando os Segmentos RFM pela média e quantidade.
df_RFM_Segment_AGG = df_RFM.groupby('RFM_Segment').agg({'Recency': 'mean','Frequency': 'mean', 'Monetary': ['mean', 'count'], 'RFM_Score': 'max'}).round(2)
df_RFM_Segment_AGG
#Prepara o dataframe para melhor exibição
df_RFM_Segment_AGG.columns = df_RFM_Segment_AGG.columns.droplevel()
df_RFM_Segment_AGG.columns = ['RecencyMean','FrequencyMean','MonetaryMean', 'Count', 'RFM_Score']
df_RFM_Segment_AGG.reset_index(inplace=True)
df_RFM_Segment_AGG.sort_values(by = 'RFM_Score', inplace=True)
df_RFM_Segment_AGG
# Plota o gráfico dos segmentos RFM
labels = df_RFM_Segment_AGG.RFM_Segment + ' (Score: ' + df_RFM_Segment_AGG.RFM_Score.map(str) + ')\nR: ' + df_RFM_Segment_AGG.RecencyMean.map(str) + '\nF: ' + df_RFM_Segment_AGG.FrequencyMean.map(str) + '\nM: ' + df_RFM_Segment_AGG.MonetaryMean.map(str) \
+ ' \nQt: ' + df_RFM_Segment_AGG.Count.map(str) + ' (' + (round(df_RFM_Segment_AGG.Count / df_RFM_Segment_AGG.Count.sum() * 100, 1) ).map(str) + ' %)'
size = df_RFM_Segment_AGG.Count
color_values =sorted( df_RFM_Segment_AGG.RFM_Score.values, reverse=True)
cmap = matplotlib.cm.Blues
mini=min(color_values)
maxi=max(color_values)
norm = matplotlib.colors.Normalize(vmin=mini, vmax=maxi)
colors = [cmap(norm(value*1.5)) for value in color_values]
fig = plt.gcf()
ax = fig.add_subplot()
fig.set_size_inches(24, 10)
squarify.plot(sizes=size,
label=labels,
alpha=.8,
color=colors
)
plt.title("\nDistribuição dos Segmentos RFM\n",fontsize=18,fontweight="bold")
plt.axis('off')
plt.show()
# Adiciona os dados de RFM no dataframe tratado.
df_full_tratado.loc[:, 'RFM_Score'] = df_RFM['RFM_Score']
df_full_tratado.loc[:, 'RFM_Segment'] = df_RFM['RFM_Segment']
df_RFM.describe()
# Seleciona os atributos previsores
atributos_previsores = df_RFM.iloc[:, 1:4]
atributos_previsores.shape
# Distribuição das varíaveis RFM - Recência (R), Frequência (F), Monetaridade (M).
print('\nDistribuição das varíaveis RFM - Recência (R), Frequência (F), Monetaridade (M)\n')
plt.figure(figsize=(12,10))
plt.subplot(3, 1, 1); sns.distplot(atributos_previsores['Recency'])
plt.subplot(3, 1, 2); sns.distplot(atributos_previsores['Frequency'])
plt.subplot(3, 1, 3); sns.distplot(atributos_previsores['Monetary'])
plt.show()
# Tratar os valores zeros antes da tranformação de Log
atributos_previsores['Recency'] = atributos_previsores['Recency'] + 0.0000000001
atributos_previsores['Frequency'] = atributos_previsores['Frequency'] + 0.0000000001
atributos_previsores['Monetary'] = atributos_previsores['Monetary'] + 0.0000000001
# Aplica a tranformação de Log para deixar os dados mais próximo da distribuição normal.
# Com isso vamos suavizar as diferenças entre os valores extremos e os demais:
atributos_previsores_log = atributos_previsores[['Recency', 'Frequency', 'Monetary']].apply(np.log, axis = 1).round(3)
# Distribuição das variáveis RFM - Recência (R), Frequência (F), Monetaridade (M) após a tranformação de Log.
print('\nDistribuição das varíaveis RFM - Recência (R), Frequência (F), Monetaridade (M) após a tranformação de Log.\n')
plt.figure(figsize=(12,10))
plt.subplot(3, 1, 1); sns.distplot(atributos_previsores_log['Recency'])
plt.subplot(3, 1, 2); sns.distplot(atributos_previsores_log['Frequency'])
plt.subplot(3, 1, 3); sns.distplot(atributos_previsores_log['Monetary'])
plt.show()
# StandardScaler para colocar todos os valores na mesma escala (escalonamento).
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
atributos_previsores_norm = scaler.fit_transform(atributos_previsores_log)
atributos_previsores_norm.shape
# Verificando a quantidade de Clusters ideal para este conjunto de dados.
# Basicamente existem três formas de fazer isso:
# 1 - Conhecimento Prévio da quatidade de Clusters;
# 2 - Sem conhecimento prévio da quatidade de Clusters, podemos usar a regra geral dada pela fórmula: cluters = Raiz quadrada de N/2, onde N é quatidade de registros;
# 3 - Utilizando o Elbow Method;
# Aqui vamos usar o Elbow Method (https://en.wikipedia.org/wiki/Elbow_method_(clustering))
# Após o valor indicado pelo “cotovelo” (curva acentuada) no gráfico indica que não possui ganho em relação ao aumento de clusters.
# Nesse ponto, significa que o método encontrou o número ideal para o argumento k.
# Obs: Nenhum destes métodos garante encontrar a quantidade ideal de cluters, para isso é necessário realizar diversos testes na base de dados.
# https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html#sklearn.cluster.KMeans
# Para este estudo vamos considerar o número de Clusters igual a 5.
from sklearn.cluster import KMeans
# Within cluster sum of squares
wcss = []
for i in range(1, 11):
kmeans = KMeans(n_clusters = i, random_state = 1)
kmeans.fit(atributos_previsores_norm)
wcss.append(kmeans.inertia_)
plt.figure(figsize = (10 ,5))
plt.plot(range(1, 11), wcss, 'o')
plt.plot(np.arange(1 , 11) , wcss , '-' , alpha = 0.5)
plt.xlabel('Clusters (k)')
plt.ylabel('Sum Squared Errors (SSE)')
plt.title('Elbow Method');
# Cria a função para calcular o centroide dos clusters
def cacular_centroide(x, y):
return (np.sum(x)/len(x), np.sum(y)/len(y))
# Cria a função para Plotar os graficos dos agrupamentos
def plota_graficos_clusters(atributos, previsoes, nome_algoritmo, x_label, y_label):
colors = ['tab:blue','tab:green','tab:red','tab:orange','tab:purple','tab:cyan','tab:olive','tab:pink','tab:gray','gold']
markers = ['*', '^', 'X', '+', 'D', 'H', 'o', '+', 's', 'v']
plt.figure(figsize=(16,6))
plt.title('Previsões Algoritmo ' + nome_algoritmo)
for i in np.unique(previsoes):
x = atributos[previsoes == i][x_label].values
y = atributos[previsoes == i][y_label].values
centroide = cacular_centroide(x,y)
if i < 0:
nome = 'Sem Cluster ' + str(i)
plt.scatter(x, y, s = 50, c = 'gray', label = nome, alpha=.7)
plt.scatter(centroide[0], centroide[1], marker = markers[i+1], s = 100, c = 'black', label = (nome + '-Centoide'))
else:
nome = 'Cluster ' + str(i+1)
plt.scatter(x, y, s = 50, c = colors[i], label = nome, alpha=.7)
plt.scatter(centroide[0], centroide[1], marker = markers[i+1], s = 100, c = 'black', label = (nome + '-Centoide'))
plt.xlabel(x_label)
plt.ylabel(y_label)
plt.legend()
plt.show()
# Cria o modelo de agrupamgento KMeans
kmeans = KMeans(n_clusters = 5, random_state = 0)
previsoes_kmeans = kmeans.fit_predict(atributos_previsores_norm)
# Adiciona a coluna com o rotulo do cluster no dataframe RFM original
df_kmeans = df_RFM.assign(Cluster = kmeans.labels_)
# Observando as Estatística descritiva dos clusters.
df_kmeans[['Recency','Frequency', 'Monetary', 'Cluster']].groupby("Cluster").describe().round(0)
# Agrupa o dataframe pelos clusters
df_kmeans_AGG = df_kmeans.groupby(['Cluster']).agg({'Recency': 'mean', 'Frequency': 'mean', 'Monetary': ['mean', 'count']}).round(2)
df_kmeans_AGG
df_kmeans.shape
# Plota o grafico com os agrupamentos
plota_graficos_clusters(atributos_previsores,previsoes_kmeans, 'Kmeans', 'Recency', 'Monetary')
# Plota o grafico com os agrupamentos
plota_graficos_clusters(atributos_previsores,previsoes_kmeans, 'Kmeans', 'Frequency', 'Monetary')
# Plota o grafico com os agrupamentos
plota_graficos_clusters(atributos_previsores,previsoes_kmeans, 'Kmeans', 'Frequency', 'Recency')
# Plota a distribuição dos clusters
plota_grafico_violino(df_kmeans, ['Recency', 'Frequency', 'Monetary'], 'Cluster', 'Distribuição das variáveis nos Clusters', total_colunas=2)
#Prepara o dataframe para melhor exibição
df_kmeans_AGG.columns = df_kmeans_AGG.columns.droplevel()
df_kmeans_AGG.columns = ['RecencyMean','FrequencyMean','MonetaryMean', 'Count']
df_kmeans_AGG.reset_index(inplace=True)
#df_kmeans_AGG.sort_values(by = 'RFM_Score', inplace=True)
df_kmeans_AGG
# Distribuição das variáveis por Cluster
trace1 = go.Bar(x= df_kmeans_AGG.Cluster, y= df_kmeans_AGG.RecencyMean , text=df_kmeans_AGG.RecencyMean , textposition = 'auto', marker=dict(color=['blue', 'deepskyblue', 'purple', 'tan']) , opacity=0.6)
trace2 = go.Bar(x= df_kmeans_AGG.Cluster, y= df_kmeans_AGG.FrequencyMean, text=df_kmeans_AGG.FrequencyMean, textposition = 'auto', marker=dict(color=['blue', 'deepskyblue', 'purple', 'tan']) , opacity=0.6 )
trace3 = go.Bar(x= df_kmeans_AGG.Cluster, y= df_kmeans_AGG.MonetaryMean , text=df_kmeans_AGG.MonetaryMean , textposition = 'auto', marker=dict(color=['blue', 'deepskyblue', 'purple', 'tan']) , opacity=0.6)
trace4 = go.Bar(x= df_kmeans_AGG.Cluster, y= df_kmeans_AGG.Count , text=df_kmeans_AGG.Count , textposition = 'auto', marker=dict(color=['blue', 'deepskyblue', 'purple', 'tan']) , opacity=0.6)
fig = make_subplots(rows=2, cols=2,
subplot_titles=("Distribuição da Média da Recência por cluster", "Distribuição da Média da Frequência por cluster", "Distribuição da Média do Valor por cluster", "Distribuição dos Clientes por cluster")
,vertical_spacing= 0.2)
fig.append_trace(trace1, 1, 1)
fig.append_trace(trace2, 1, 2)
fig.append_trace(trace3, 2, 1)
fig.append_trace(trace4, 2, 2)
fig.update_yaxes(row=1, col=1, title_text='RecencyMean')
fig.update_yaxes(row=1, col=2, title_text='FrequencyMean')
fig.update_yaxes(row=2, col=1, title_text='MonetaryMean')
fig.update_yaxes(row=2, col=2, title_text='Quantity')
fig.update_xaxes(row=1, col=1, title_text='Cluster', showline=False, linecolor='gray', tickmode='linear')
fig.update_xaxes(row=1, col=2, title_text='Cluster', showline=False, linecolor='gray', tickmode='linear')
fig.update_xaxes(row=2, col=1, title_text='Cluster', showline=False, linecolor='gray', tickmode='linear')
fig.update_xaxes(row=2, col=2, title_text='Cluster', showline=False, linecolor='gray', tickmode='linear')
fig.update_layout(plot_bgcolor='rgba(0,0,0,0)' , showlegend=False, height=600)
fig.show()
# Adiciona o Cluster no dataframe tratado.
df_full_tratado.loc[:,'Cluster' ] = df_kmeans['Cluster']
df_full_tratado.head()
# Salva do Dataframe em CSV
#df_kmeans.to_csv('df_kmeans.csv', index=False, sep=';', encoding='utf-8-sig')
# Vamos tratar o atributo Marital_Status
atributo_marital = df_full_tratado['Marital_Status'].values
atributo_marital.shape
atributo_marital
# Vamos realizar a codificação dos atributos categóricos em valores numéricos. Além disso, vai também realizar a binarização das variáveis categóricas. Técnica chamada: Dummy variables.
# Isto é importante para que o algoritmo não considere um valor maior mais importante que um valor menor, por exemplo.
from sklearn.preprocessing import OneHotEncoder
oneHotEnconder = OneHotEncoder(categories='auto')
atributo_marital_oh = oneHotEnconder.fit_transform(atributo_marital.reshape(-1,1)).toarray()
atributo_marital_oh.shape
atributo_marital_oh
# Vamos tratar o atributo Education
atributo_education = df_full_tratado['Education'].values
atributo_education.shape
atributo_education
# Vamos Realizar a codificação dos atributos categóricos em valores numéricos. Porém, como neste atributo existe uma "Ordem", pode ser importante fornecer essa infomação para o modelo.
# Por isso, vamos codificar ele usando o Ordinal Enconder para transformar as categorias em números ordinais.
from sklearn.preprocessing import OrdinalEncoder
ordinalEncoder= OrdinalEncoder(categories=[['Basic', '2n Cycle', 'Graduation','Master', 'PhD']])
atributo_education_oe = ordinalEncoder.fit_transform(atributo_education.reshape(-1,1))
atributo_education_oe.shape
atributo_education_oe
df_full_tratado.columns
df_full_tratado.shape
#df_full_tratado.hist(figsize=(30,20));
# Vamos definir os atributos numéricos que usaremos como previsores.
# OBS: O atributos ID está na lista apenas permitir a ratreabilidade do registro. Ele não será inserido no modelo.
coluna_previsores_numericos = ['ID', 'Income', 'Kidhome', 'Teenhome', 'Recency', 'MntWines', 'MntFruits', 'MntMeatProducts', 'MntFishProducts', 'MntSweetProducts', 'MntGoldProds', 'NumDealsPurchases', 'NumWebPurchases', 'NumCatalogPurchases', 'NumStorePurchases', 'NumWebVisitsMonth', 'AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5', 'AcceptedCmp1', 'AcceptedCmp2', 'Complain', 'Age', 'NumOffSprings', 'MntTotal', 'NumTotal', 'TotalAcceptedCmp', 'Prop_AcceptedCmp', 'PropWines', 'PropFruits', 'PropMeatProducts', 'PropFishProducts', 'PropSweetProducts', 'PropGoldProds', 'RFM_Score']
previsores_numericos = df_full_tratado[coluna_previsores_numericos]
previsores_numericos.shape
# Vamos unir os atributos previsores categóricos e númericos.
atributos_previsores= np.concatenate((previsores_numericos, atributo_marital_oh, atributo_education_oe), axis=1)
atributos_previsores.shape
# Seleciona o atributo Target
atributo_target = df_full_tratado['Response']
atributo_target.shape
atributo_target
atributos_previsores
# Para o escalonamento dos dados, vamos utilizar a Padronização dos dados para ficaram na mesma escala.
# O escalonamento também ajuda no desempenho de algums algoritmos.
# Essa técnica na prática ignora a forma da distribuição e transforma o dado para forma com média próxima de zero e um desvio padrão próximo a um, ou seja, assume que não temos valores discrepantes nos dados e normaliza tudo.
atributos_previsores_pad = atributos_previsores
from sklearn.preprocessing import StandardScaler
#Padroniza os dados de X
scale_x = StandardScaler()
# Vamos deixar o atributo ID fora da padronização
atributos_previsores_pad[:, 1:atributos_previsores.shape[1]] = scale_x.fit_transform(atributos_previsores[:, 1:atributos_previsores.shape[1]].astype(float).astype(float))
atributos_previsores_pad.shape
atributos_previsores_pad
# Análise de componetes principais
from sklearn.decomposition import PCA
X = atributos_previsores_pad[:, 1:atributos_previsores_pad.shape[1]] # Retira o atributo ID .
y = atributo_target
# PCA
pca = PCA(n_components=X.shape[1])
pca.fit(X)
X_pca = pca.transform(X)
# O percentual da explicação das variáveis de acordo com o número de componentes principais (Variância explicada).
df_pca = pd.DataFrame(pca.explained_variance_ratio_.cumsum(), columns=['% Explicação'], index=range(1, pca.n_components+1))
df_pca.index.name = 'Qtd Componentes'
df_pca.transpose()
# Na tabela acima podemos ver que para ter um percentual aproximado de 99% de explicação do dados, iremos precisar de pelo 29/30 componentes.
# Por hora não vamos usar os componentes PCA.
atributo_target.shape
atributo_target.value_counts()
# Define o método para plotar a Distribuição da variável Target.
def plota_grafico_target(series_target, titulo):
categoria= series_target.unique()
dados = series_target.value_counts()
fig = go.Figure()
fig.add_trace(go.Bar(x=categoria, y=dados, text=dados , textposition = 'auto', marker=dict(color=['dodgerblue', 'indianred'])))
fig.update_layout(
title=
{
'text': titulo ,
'y':0.95,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'
} ,
plot_bgcolor='rgba(0,0,0,0)',
yaxis=dict(title='Quantidade'),
xaxis=dict(title='Response', tickmode='linear' , showline=True, linewidth=1, linecolor='gray') , barmode='stack', width=800, height=400
)
fig.show()
plota_grafico_target(atributo_target, 'Distribuição da variável Target (Response)')
# Como observamos acima, a variável target não está distribuída uniformimente no dataset, vamos precisar equilibrar essa distribuição para evitar viés nos algoritmos.
# Temos poucos registro para a classe 1, por isso vamos usar uma técnica chamada de OVER SAMPLING para aumentar a quantidade de observações da classe 1.
# Para isso vamos utilizar o SMOTE - Synthetic Minority Over-sampling Technique (https://imbalanced-learn.readthedocs.io/en/stable/generated/imblearn.over_sampling.SMOTE.html)
from imblearn.over_sampling import SMOTE
smote = SMOTE()
atributos_previsores_smote, atributo_target_smote = smote.fit_resample(atributos_previsores_pad, atributo_target)
atributo_target_smote.shape
atributo_target_smote.value_counts()
# Plota a Distribuição da variável Target (Response) após a aplicação OVER SAMPLING
plota_grafico_target(atributo_target_smote, 'Distribuição da variável Target (Response) após o Over Sampling' )
# Importando as bibliotecas dos modelos
from sklearn import model_selection
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
import xgboost
from datetime import datetime
# Defindo os parâmetos do FOLD para a Validação cruzada (Cross-validation)
qtd_instancias = len(atributos_previsores)
qtd_folds = 10
seed = 3
# Criando a lista de modelos para avaliação.
modelos = []
modelos.append(('AdaBoostClassifier', AdaBoostClassifier(random_state=0)))
modelos.append(('GradientBoostingClassifier', GradientBoostingClassifier(random_state=0)))
modelos.append(('RandomForestClassifier', RandomForestClassifier(n_estimators=20, criterion='entropy', random_state=0)))
modelos.append(('ExtraTreesClassifier', ExtraTreesClassifier(n_estimators=20, criterion='entropy', random_state=0)))
modelos.append(('DecisionTreeClassifier', DecisionTreeClassifier(random_state=0)))
modelos.append(('KNeighborsClassifier', KNeighborsClassifier()))
modelos.append(('NaiveBayes', GaussianNB( )))
modelos.append(('LogisticRegression', LogisticRegression(solver='lbfgs', multi_class='auto', max_iter=10000, random_state=0)))
modelos.append(('SVC', SVC(random_state=0)))
modelos.append(('xgboost', xgboost.XGBClassifier(random_state=0)))
modelos.append(('Redes Neurais', MLPClassifier(verbose = False, max_iter=1000, tol = 0.0000010, solver = 'adam', hidden_layer_sizes=(100), activation='relu', random_state=0)))
# Definindo as listas para análise dos resultados.
resultados = []
nomes_modelos = []
tempo_treinamento_modelos = []
previsores = atributos_previsores_smote[:, 1:atributos_previsores_smote.shape[1]] # Retira o atributo ID .
target = atributo_target_smote.values.ravel()
print('Resultado da Validação cruzada (Cross-validation) dos algoritmos:\n')
# Treina os Modelos
for nome_modelo, modelo in modelos:
kfold = model_selection.KFold(n_splits=qtd_folds, random_state=seed, shuffle=True)
#print(nome_modelo)
#Define o tempo de inicio do Treinamento
inicio = datetime.now()
resultados_cros_val = model_selection.cross_val_score(modelo, previsores, target, cv=kfold, scoring='accuracy')
#Define o tempo final do Treinamento
fim = datetime.now()
tempo_modelo = (fim - inicio).total_seconds()
tempo_treinamento_modelos.append( tempo_modelo)
resultados.append(resultados_cros_val)
nomes_modelos.append(nome_modelo)
print('{:28s} Acurácia: {:6.4f} | Desvio Padrão: {:6.4f} | Tempo de Treinamento: {:6.2f} segundos'.format(nome_modelo, resultados_cros_val.mean(), resultados_cros_val.std(), tempo_modelo))
# Plota o gráfico para Comparação da acurácia dos Algoritmos
fig = go.Figure()
for model, result in zip(modelos, resultados):
fig.add_trace(go.Box(y=result , name = model[0]))
fig.update_layout( title={'text': 'Comparação dos Algoritmos de Classificação', 'y':0.95, 'x':0.5, 'xanchor': 'center', 'yanchor': 'top'} ,
showlegend=False
)
fig.update_yaxes(title_text='acurácia', showline=True, linewidth=1, linecolor='gray')
fig.update_xaxes(showline=True, linewidth=1, linecolor='gray')
fig.show()
#Plota um gráfico de Comparação dos Tempo de treinamento dos Algoritmos de Classificação
cores = ['rgb(31, 119, 180)',
'rgb(255, 127, 14)',
'rgb(44, 160, 44)',
'rgb(214, 39, 40)',
'rgb(148, 103, 189)',
'rgb(140, 86, 75)',
'rgb(227, 119, 194)',
'rgb(127, 127, 127)',
'rgb(188, 189, 34)',
'rgb(23, 190, 207)',
'rgb(223, 170, 197)']
trace0= go.Bar(y=tempo_treinamento_modelos, x=nomes_modelos, textposition = 'auto', text=tempo_treinamento_modelos, marker=dict(color=cores),opacity=0.7 )
data=[trace0]
layout = go.Layout(yaxis=dict(title='segundos') ,title='Tempo para treinamento dos Algoritmos de Classificação')
fig = go.Figure(data=data, layout=layout )
fig.show()
# O algoritmo de classificação que obteve o melhor resultado nos testes acima foi o ExtraTreesClassifier. Vamos selecionar ele.
# Criando o Modelo
classificador = ExtraTreesClassifier(n_estimators=20, random_state=0)
# Separando os dados de Treinamento e Teste
from sklearn.model_selection import train_test_split
X_treinamento, X_teste, y_treinamento, y_teste = train_test_split(atributos_previsores_smote, atributo_target_smote.values.ravel(), test_size = 0.3)
print('Shape dos dados de Treinamento')
print(X_treinamento.shape)
print(y_treinamento.shape)
print('Shape dos dados de Teste')
print(X_teste.shape)
print(y_teste.shape)
# Treinando o modelo selecionado
# Retira o atributo ID antes do input no modelo
X_treinamento_model = X_treinamento[:, 1:X_treinamento.shape[1]]
classificador.fit(X_treinamento_model, y_treinamento)
classificador.get_params()
# Realiza a previsão com os dados de Teste
X_teste_model = X_teste[:, 1:X_teste.shape[1]]
y_previsto = classificador.predict(X_teste_model)
# Precisão do modelo com os dados de teste
print('Acurácia do modelo com os dados de testes')
classificador.score(X_teste_model, y_teste).round(4)
# Identificando o nome da classe de acordo com a codificação
# Não Respondeu = 0 #Respondeu = 1
nome_classe = ['NãoRespondeu', 'Respondeu',]
# Relatório de Classificação
#precision - daqueles que classifiquei como corretos, quantos efetivamente estavam corretos?
#recall - quando realmente é da classe XPTO, o quão frequente você classifica como XPTO?
#f1-score - balanço entre a precisão e o recall. Combina precisão e recall em um número único que indique a qualidade geral do seu modelo (quanto maior melhor o modelo).
#support - número de ocorrência de cada classe.
from sklearn.metrics import classification_report
relatorio_classificacao = classification_report(y_teste, y_previsto, target_names=nome_classe ,digits=4)
print('\tRelatório de Classificação do modelo\n')
print(relatorio_classificacao)
# Visualizar a Matriz de confusão (confusion matrix)
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_teste, y_previsto)
df_cm = pd.DataFrame(cm, nome_classe, nome_classe)
print('Obs.: Nas linhas temos as Classes reais e nas colunas as Classes previstas.\n')
print('\tMatriz de Confusão')
df_cm
# Vamos inibir os avisos de FutureWarning.
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
# Vamos importar a biblioteca Yellowbrick. Esta biblioteca nos ajuda no diagnóstico e visualização de modelos de Machine Learning.
from yellowbrick.classifier import ROCAUC
# Vamos plotar o gráfico das curvas ROC e AUC com a avaliação feita com a yellowbrick.
# A curva ROC nos mostra o quão bom está nosso modelo para poder distinguir entre duas coisas (já que é utilizado para classificação). Ela possui dois parâmtros importantes:
# Taxa de verdadeiro positivo (True Positive Rate), que tem a seguinte fórmula: true positives / (true positives + false negatives).
# Taxa de falso positivo (False Positive Rate), que tem a seguinte fórmula: false positives / (false positives + true negatives).
# O valor da curva AUC varia no intervalo de 0 até 1 e o limiar entre a classe é 0,5. Ou seja, acima desse limite, o algoritmo classifica em uma classe e abaixo na outra classe.
# Quanto maior o AUC, melhor. O AUC trabalha com precisão das classificações e não com os valores absolutos.
visualizer = ROCAUC(classificador, classes=[0, 1])
visualizer.fit(X_treinamento_model, y_treinamento)
visualizer.score(X_teste_model, y_teste)
visualizer.show() ;
# Reverte a alteração da escala feita anteriormente
X_teste_scale = X_teste
X_teste_scale = scale_x.inverse_transform( X_teste[:, 1:X_teste.shape[1]])
X_teste_scale.shape
# Extrai o atributo Marital_Status condificado.
atrib_marital_oh = X_teste_scale[:, 34:42]
atrib_marital_oh.shape
# Reverte a codificação do OneHotEnconder feita anteriormente para os valores originais.
atrib_marital_orig = oneHotEnconder.inverse_transform(atrib_marital_oh)
atrib_marital_orig.shape
atrib_marital_orig
# Extrai o atributo Education condificado.
atrib_education_oe = X_teste_scale[:, 40:41]
atrib_education_oe.shape
# Reverte a alteração do OrdinalEncoder feita anteriormente para os valores originais.
atrib_education_orig = ordinalEncoder.inverse_transform(atrib_education_oe)
atrib_education_orig.shape
atrib_education_orig
# Extrai os atributos restantes.
X_teste_orig = X_teste_scale[:, 0:35]
X_teste_orig.shape
X_teste_orig[0]
X_teste_orig = np.concatenate((X_teste_orig, atrib_marital_orig, atrib_education_orig), axis=1)
X_teste_orig.shape
X_teste_orig[0]
colunas = coluna_previsores_numericos.copy()
colunas.append('Marital_Status')
colunas.append('Education')
len(colunas)
# Cria um dataframe com os dados de testes.
df_previsores = pd.DataFrame(X_teste_orig, columns=colunas)
df_previsores.head()
# Cria um Dataframe com a classe Prevista
df_prev = pd.DataFrame(y_previsto, columns=['CLASS_PREDICT'])
# Cria um Dataframe com a classe Real
df_real = pd.DataFrame(y_teste, columns=['CLASS_REAL'])
# Junta os dois dataframes
df_target = pd.merge(df_real, df_prev, right_index=True, left_index=True)
df_target['PREDICT_SUCCESS'] = df_target.CLASS_PREDICT == df_target.CLASS_REAL
# Agora juntando os dataframes de previsores e target
df_merge = pd.merge(df_previsores, df_target, right_index=True, left_index=True)
# Exibindo a quantidade de acertos e erros da previsão
print('\nQuantidade de acertos e erros da previsão')
df_merge[['ID', 'PREDICT_SUCCESS']].groupby(['PREDICT_SUCCESS']).count()
# Exibindo somente os registros onde houve erro na previsão da classe.
print('\nRegistros com erros na previsão')
df_merge[df_merge.PREDICT_SUCCESS==False]
# Salva do Dataframe em CSV
#df_merge.to_csv('df_teste_previsoes_modelo.csv', index=False, sep=';', encoding='utf-8-sig')
Como podemos observar nesse breve estudo, o uso de modelos preditivos podem vir a ser uma poderosa ferramenta de apoio a área de marketing na segmentação e classificação de clientes.
É claro que esse estudo é apenas uma POC (prova de conceito) e modelagem apresentada aqui pode não ter o mesmo desempenho em um cenário real. Além disso, os resultados nos mostram que ainda há bastante espaço para melhorias, seja no tratamento e preparação dos dados, seleção das variáveis para o modelo e também na escolha dos algoritmos.
Mesmo assim, fica evidente que pode valer a pena fazer uso destas técnicas, mesmo que o modelo em um ambiente real tenha um performance razoável, podemos economizar tempo e dinheiro limitando contatos com clientes que não têm interesses nos produtos oferecidos. Como também, direcionar melhor as campanhas de marketing de acordo com o perfil do cliente.